<template>
  <div
    ref="CanvasBox"
    class="canvas-box"
    :style="{ aspectRatio: currentArea.aspectRatio }"
    @mousewheel="onMousewheel"
    @mousedown="rootMousedown"
  >
    <img :src="currentArea.imgUrl" style="display: block; width: 100%">
    <div
      v-if="(readonly || deviceDraggable) && !editPart"
      ref="containerRef"
      class="device-box width-full height-full"
    >
      <i
        v-for="(item, index) in devicesPointScope"
        ref="dragRef"
        :key="item.devId"
        class="point iconfont"
        :class="[`icon-${item.type === 2 ? 'wangguandianwei' : 'shebeidianwei'}`, { current: currentDevice.devId === item.devId }]"
        draggable="false"
        :style="{
          top: item.point.y + 'px',
          left: item.point.x + 'px',
          cursor: deviceDraggable ? 'move' : 'default',
          color: colorMap[item.state]
        }"
        @click="handleDeviceDetail(item)"
        @mousedown.stop="handleMouseDown($event, item, index)"
      ></i>
    </div>
  </div>
</template>
<script>
import { mat4 } from 'gl-matrix'
import useDraw from '@/utils/useDraw.js'
import { indoorMove, roomCheck } from '@/api/point'
export default {
  name: 'RoomEdit',
  props: {
    currentNode: {
      required: true,
      type: Object,
      default() {
        return {
          aspectRatio: '788/942'
        }
      }
    },
    devicesPoint: {
      required: false,
      type: Array,
      default() { return [] }
    },
    areaInfo: {
      required: false,
      type: Array,
      default() { return [] }
    },
    // 可增加区域
    roomAddable: {
      require: false,
      type: Boolean,
      default: true
    },
    currentDevice: {
      required: false,
      type: Object,
      default: () => ({})
    },
    // 是否全屏
    isFullScreen: {
      require: false,
      type: Boolean,
      default: false
    },
    // 是否开启图形编辑
    editPart: {
      require: false,
      type: Boolean,
      default: false
    },
    // 设备可拖拽
    deviceDraggable: {
      require: false,
      type: Boolean,
      default: false
    },
    readonly: { // 只读模式
      require: false,
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      debounceTimer: null,
      boxOr: null,
      BaseWidth: 500, // 传给后端的数据始终基于宽度500等比例缩放
      scale: 1, // 缩放比例
      zoom: 1, // 用于限制空间缩放比例
      dragRoot: {},
      devicesPointScope: [], // 缩放过的点位数据
      colorMap: ['#FF5757', '#2885FF', '#5F86BA', '#FF37E4', '#909090'],
      dragItem: {},
      drawer: null
    }
  },
  computed: {
    currentArea() {
      let res = {}
      if (this.currentNode.nodeType === 'area') {
        res = this.currentNode
      } else if (this.currentNode.nodeType === 'room') {
        res = this.currentNode.fullPath[1]
      }
      return res
    }
  },
  watch: {
    areaInfo: {
      deep: true,
      handler() {
        const { width, height } = this.$refs.CanvasBox.getBoundingClientRect()
        this.scale = width / this.BaseWidth
        const res = this.areaInfo.map(x => ({
          ...x,
          points: x.points.map(y => ({
            x: y.x * this.scale,
            y: y.y * this.scale
          }))
        }))
        this.drawer.ctxResClear()
        this.drawer.updateAddable(this.roomAddable && !this.readonly && !this.deviceDraggable)
        this.drawer.drawAreaByPoints(this.drawer.getCtxRes(), res)
      }
    },
    devicesPoint: {
      deep: true,
      handler() {
        this.devicesPointScope = JSON.parse(JSON.stringify(this.devicesPoint)).map(x => {
          x.point.x = x.point.x * this.scale
          x.point.y = x.point.y * this.scale
          return x
        })
      }
    },
    currentNode() {
      this.adaptiveView()
    },
    isFullScreen() {
      this.adaptiveView()
    }
  },
  created() {
    this.drawer = useDraw()
  },
  // 初始化
  mounted() {
    this.drawerInit()
    this.boxOr = new ResizeObserver(entries => {
      this.adaptiveView()
    })
    this.boxOr && this.boxOr.observe(this.$el.parentElement)
  },
  beforeDestroy() {
    this.boxOr && this.boxOr.unobserve(this.$el.parentElement)
  },
  methods: {
    drawerInit() {
      this.drawer.init(this.$refs.CanvasBox, {
        // 传入一些选项。可选
        // 绘制完成时的回调
        onComplete: (points) => {
          const res = JSON.parse(JSON.stringify(points))
          res.forEach(x => {
            x.points = x.points.map(y => {
              return {
                x: y.x / this.scale,
                y: y.y / this.scale
              }
            })
          })
          // 相交校验
          roomCheck({
            roomList: res.filter(x => x.readonly),
            roomDto: res.filter(x => !x.readonly)[0]
          }).then(() => {
            // console.log((JSON.stringify(res, null, 2)))
            this.$emit('complete', res)
          }).catch(err => {
            this.$message.error(err.msg)
            const index = res.findIndex(x => x.readonly === false)
            // 新增的区域
            if (index === -1) {
              this.$emit('complete', res.slice(0, -1))
            } else { // 编辑的区域
              this.$emit('complete', [...this.areaInfo])
            }
          })
        },
        roomAddable: this.roomAddable && !this.readonly && !this.deviceDraggable,
        // 报错时回调
        onError: (msg) => {
          this.$message.error(msg)
        },
        // 清空时回调
        onClear: () => {}
      })
    },
    rootMousedown(e) {
      this.dragRoot.ref = this.$el
      e = e || window.event
      const { offsetLeft, offsetTop } = this.$el
      this.dragRoot.diffX = e.clientX - offsetLeft
      this.dragRoot.diffY = e.clientY - offsetTop
      document.addEventListener('mousemove', this.rootMousemove)
      document.addEventListener('mouseup', this.rootMouseup)
      return false
    },
    rootMousemove(e) {
      e = e || window.event
      this.dragRoot.left = e.clientX - this.dragRoot.diffX
      this.dragRoot.top = e.clientY - this.dragRoot.diffY

      // 实时给元素定位赋值
      this.dragRoot.ref.style.left = this.dragRoot.left + 'px'
      this.dragRoot.ref.style.top = this.dragRoot.top + 'px'
    },
    rootMouseup() {
      document.removeEventListener('mousemove', this.rootMousemove)
      document.removeEventListener('mouseup', this.rootMouseup)
    },
    // 鼠标滚动缩放
    onMousewheel(e) {
      e.preventDefault()
      const bodyZoom = document.body.style.zoom
      const { clientX, clientY, deltaY } = e
      let scale = 1 + (deltaY < 0 ? 0.1 : -0.1)
      scale = Math.max(scale > 0 ? scale : 1, 0.1)
      const tempZoom = 1 + (deltaY < 0 ? 0.1 : -0.1)

      this.zoom = parseFloat((this.zoom * tempZoom).toFixed(2))
      if (this.zoom > 9) {
        this.zoom = 9
        return
      } else if (this.zoom < 0.1) {
        this.zoom = 0.1
        return
      }
      const { top, right, bottom, left } = this.$el.getBoundingClientRect()
      const parsenTop = this.$el.parentElement.getBoundingClientRect().top
      const parsenLeft = this.$el.parentElement.getBoundingClientRect().left
      const o = new Float32Array([
        left, top, 1, 1,
        right, top, 1, 1,
        right, bottom, 1, 1,
        left, bottom, 1, 1
      ])
      const x = clientX * (1 - scale) / bodyZoom
      const y = clientY * (1 - scale) / bodyZoom
      const t = new Float32Array([
        scale, 0, 0, 0,
        0, scale, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
      ])
      const m = new Float32Array([
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        x, y, 0, 1
      ])
      // 在XY轴上进行缩放
      const res1 = mat4.multiply(new Float32Array([
        0, 0, 0, 0,
        0, 0, 0, 0,
        0, 0, 0, 0,
        0, 0, 0, 0
      ]), t, o)
      // 在XY轴上进行平移
      const res2 = mat4.multiply(new Float32Array([
        0, 0, 0, 0,
        0, 0, 0, 0,
        0, 0, 0, 0,
        0, 0, 0, 0
      ]), m, res1)
      this.$el.setAttribute('style', `left: ${res2[0] - parsenLeft}px; top: ${res2[1] - parsenTop}px;width: ${res2[4] - res2[0]}px;aspect-ratio: ${this.currentArea.aspectRatio};transform: none;`)

      this.recalcCanvas()
    },
    adaptiveView() {
      if (this.debounceTimer !== null) {
        clearTimeout(this.debounceTimer)
      }
      this.debounceTimer = setTimeout(async() => {
        this.zoom = 1
        this.scaleMap()
        this.recalcCanvas()
      }, 100)
    },
    recalcCanvas() {
      const { clientWidth } = this.$el || {}
      this.scale = clientWidth / this.BaseWidth
      const res = this.areaInfo.map(x => ({
        ...x,
        points: x.points.map(y => ({
          x: y.x * this.scale,
          y: y.y * this.scale
        }))
      }))
      this.drawer.resize()
      this.drawer.ctxResClear()
      this.drawer.drawAreaByPoints(this.drawer.getCtxRes(), res)
      this.devicesPointScope = JSON.parse(JSON.stringify(this.devicesPoint)).map(x => {
        x.point.x = x.point.x * this.scale
        x.point.y = x.point.y * this.scale
        return x
      })
    },
    scaleMap() {
      const { clientWidth, clientHeight } = this.$el || {}
      const parentWidth = this.$el.parentElement && this.$el.parentElement.clientWidth
      const parentHeight = this.$el.parentElement && this.$el.parentElement.clientHeight
      const scaleWidth = parentWidth / clientWidth
      const scaleHeight = parentHeight / clientHeight
      const transformScale = Math.min(scaleWidth, scaleHeight)
      this.$el.setAttribute('style', `width: ${clientWidth * transformScale}px;aspect-ratio: ${this.currentArea.aspectRatio};`)
    },
    debounce(fn, delay) {
      let time = null // time用来控制事件的触发
      return function() {
        if (time !== null) {
          clearTimeout(time)
        }
        time = setTimeout(() => {
          fn.call(this)
          // 利用call(),让this的指针从指向window 转成指向input
        }, delay)
      }
    },
    handleDeviceDetail(item) {
      if (!this.readonly) return
      this.$emit('deviceClick', item.devId === this.currentDevice.devId ? {} : { ...item })
    },
    handleMouseDown(e, item, index) {
      if (!this.deviceDraggable) return
      this.dragItem.ref = this.$refs.dragRef[index]
      this.dragItem.data = JSON.parse(JSON.stringify(item))
      e = e || window.event
      this.dragItem.diffX = e.clientX - e.target.offsetLeft
      this.dragItem.diffY = e.clientY - e.target.offsetTop
      document.addEventListener('mousemove', this.docMousemove)
      document.addEventListener('mouseup', this.docMouseup)
      return false
    },
    docMousemove(e) {
      // 浏览器兼容
      e = e || window.event
      this.dragItem.left = e.clientX - this.dragItem.diffX
      this.dragItem.top = e.clientY - this.dragItem.diffY
      // 边界处理，防止超出各个边
      // 保证拖拽元素不超出画布边界，则不要超出画布减去拖拽元素本身宽高的距离
      if (this.dragItem.left < 0) {
        this.dragItem.left = 0
      } else if (this.dragItem.left > this.$refs.containerRef.clientWidth) {
        this.dragItem.left = this.$refs.containerRef.clientWidth
      }
      if (this.dragItem.top < 0) {
        this.dragItem.top = 0
      } else if (this.dragItem.top > this.$refs.containerRef.clientHeight) {
        this.dragItem.top = this.$refs.containerRef.clientHeight
      }
      // 实时给元素定位赋值
      this.dragItem.ref.style.left = this.dragItem.left + 'px'
      this.dragItem.ref.style.top = this.dragItem.top + 'px'
      return false
    },
    async docMouseup(e) {
      document.removeEventListener('mousemove', this.docMousemove)
      document.removeEventListener('mouseup', this.docMouseup)
      const cacheDataPoint = { ...this.dragItem.data.point }

      this.$confirm('确认移动设备位置？', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async() => {
        const { left, top } = this.dragItem
        const { clientWidth } = this.$refs.containerRef
        this.dragItem.data.point.x = left * this.BaseWidth / clientWidth
        this.dragItem.data.point.y = top * this.BaseWidth / clientWidth
        const { devId, point } = this.dragItem.data
        const { data } = await indoorMove({
          devId,
          point
        }).catch(err => {
          const { x, y } = cacheDataPoint
          this.dragItem.ref.style.left = x + 'px'
          this.dragItem.ref.style.top = y + 'px'
          this.$message.error(err.msg)
        })
        this.$message.success('更新成功')
        this.$emit('updateDeviceState')
      }).catch(() => {
        // 实时给元素定位赋值
        const { x, y } = cacheDataPoint
        this.dragItem.ref.style.left = x + 'px'
        this.dragItem.ref.style.top = y + 'px'
      })
    }
  }
}
</script>
<style lang="scss" scoped>
@function px($px) {
  @return $px / (1920/16) + rem;
}
.canvas-box {
  position: absolute;
  left: 0;
  top: 0;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  user-select: none;

  .device-box {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 2;
  }
}
.point {
  position: absolute;
  font-size: 16px;
  color: #457cfc;
  transform: translate(-50%, calc(-100% + px(4)));

  &:hover {
    text-shadow: 0 0 10px currentColor;
  }
}

.current {
  animation: jump 0.8s linear infinite;
}

@keyframes jump {
  0%, 100% {
    transform: translate(-50%, calc(-100% + px(4)));
  }
  40%, 60% {
    transform: translate(-50%, calc(-100% + px(0)));
  }
}
</style>
